#include <dspSimulator/instruction/bnop.h>

bool BNOP::checkCondition(std::shared_ptr<DspMachine> dspMachine) {
    if (isCompactInstruction) {
        if (compactInstKind == "Sbs7c") {
            return checkSbs7cCondition(dspMachine);
        }
        return true;
    } else {
        return checkStandardCondition(dspMachine);
    }
}

void BNOP::loadInstIRIntoJit(llvm::LLVMContext *llvmContext, llvm::Module *module,
                             std::shared_ptr<llvm::IRBuilder<>> irBuilder) {

}

std::shared_ptr<Instruction> BNOP::decode(std::shared_ptr<InstInfo> instInfo, u64 baseCycle) {
    std::shared_ptr<BNOP> bnop;
    auto dspInstInfo = std::dynamic_pointer_cast<DspInstInfo>(instInfo);
    bool isStandardInst = dspInstInfo->isStandardInstruction();
    if (isStandardInst) {
        auto data = vectorToBitSet<32>(dspInstInfo->getBits());
        if (checkBits<11>(data, 2, 0b00001001000) && existStandardCondition(data)) {
            bnop = std::make_shared<BNOP>(dspInstInfo->getAddress(), baseCycle);
            bnop->creg_z = extractBits<4>(data, 28);
            bnop->cst = extractBits<12>(data, 16).to_ulong();
            auto cst3 = extractBits<3>(data, 13).to_ulong();
            bnop->numOfInsertNOP = cst3;
            bnop->s = data[1];
            bnop->crhi = 0b00000;
            bnop->crlo = 0b10000;
            bnop->p = data[0];
            if (bnop->s == 0) {
                bnop->funcUnit = FuncUnit::S1;
            } else {
                bnop->funcUnit = FuncUnit::S2;
            }
            if (dspInstInfo->isStandardPacket()) {
                bnop->instFormat = InstFormat::Kind1;
            } else {
                bnop->instFormat = InstFormat::Kind2;
            }
        } else if (checkBits<11>(data, 1, 0b00110110001) && checkBits<2>(data, 16, 0b00) &&
                   checkBits<5>(data, 23, 0b00001) && existStandardCondition(data)) {
            bnop = std::make_shared<BNOP>(dspInstInfo->getAddress(), baseCycle);
            bnop->creg_z = extractBits<4>(data, 28);
            bnop->src2 = extractBits<5>(data, 18).to_ulong();
            auto cst3 = extractBits<3>(data, 13).to_ulong();
            bnop->numOfInsertNOP = cst3;
            bnop->x = data[12];
            bnop->s = 1;
            bnop->crhi = 0b00000;
            bnop->crlo = 0b10000;
            bnop->funcUnit = FuncUnit::S2;
            bnop->p = data[0];
            bnop->instFormat = InstFormat::Kind3;
        }
    } else {
        auto data = vectorToBitSet<16>(dspInstInfo->getBits());
        auto br = dspInstInfo->getBR();
        auto N3 = extractBits<3>(data, 13).to_ulong();
        if (br == 1 && N3 <= 5 && checkBits<5>(data, 1, 0b00101)) {
            bnop = std::make_shared<BNOP>(dspInstInfo->getAddress(), baseCycle);
            bnop->isCompactInstruction = true;
            bnop->compactInstKind = "Sbs7";
            bnop->cst = extractBitsAndToInt<7>(extractBits<7>(data, 6));
            auto cst3 = extractBits<3>(data, 13).to_ulong();
            bnop->numOfInsertNOP = cst3;
            bnop->s = data[0];
            bnop->crhi = 0b00000;     // creg point to PCE1
            bnop->crlo = 0b10000;
            if (bnop->s == 0) {
                bnop->funcUnit = FuncUnit::S1;
            } else {
                bnop->funcUnit = FuncUnit::S2;
            }
            bnop->PROT = dspInstInfo->getPROT();
            bnop->RS = dspInstInfo->getRS();
            bnop->BR = dspInstInfo->getBR();
            bnop->DSZ = dspInstInfo->getDSZ();
            bnop->SAT = dspInstInfo->getSAT();
            bnop->p = dspInstInfo->getP();
            bnop->fixUpRegOnCompactInstruction();
            bnop->instFormat = InstFormat::Kind2;
        } else if (checkBits<2>(data, 14, 0b11) && checkBits<5>(data, 1, 0b00101)) {
            bnop = std::make_shared<BNOP>(dspInstInfo->getAddress(), baseCycle);
            bnop->isCompactInstruction = true;
            bnop->compactInstKind = "Sbu8";
            bnop->cst = extractBitsAndToInt<7>(extractBits<7>(data, 6));
            bnop->numOfInsertNOP = 5;
            bnop->s = data[0];
            bnop->crhi = 0b00000;     // creg point to PCE1
            bnop->crlo = 0b10000;
            bnop->PROT = dspInstInfo->getPROT();
            bnop->RS = dspInstInfo->getRS();
            bnop->BR = dspInstInfo->getBR();
            bnop->DSZ = dspInstInfo->getDSZ();
            bnop->SAT = dspInstInfo->getSAT();
            bnop->p = dspInstInfo->getP();
            bnop->fixUpRegOnCompactInstruction();
            bnop->instFormat = InstFormat::Kind2;
        } else if (N3 <= 5 && br && data[5] == 1 && checkBits<3>(data, 1, 0b101)) {
            bnop = std::make_shared<BNOP>(dspInstInfo->getAddress(), baseCycle);
            bnop->isCompactInstruction = true;
            bnop->compactInstKind = "Sbs7c";
            bnop->cst = extractBitsAndToInt<7>(extractBits<7>(data, 6));
            auto cst3 = extractBits<3>(data, 13).to_ulong();
            bnop->numOfInsertNOP = cst3;
            bnop->z = data[4];
            bnop->s = data[0];
            bnop->crhi = 0b00000;     // creg point to PCE1
            bnop->crlo = 0b10000;
            if (bnop->s == 0) {
                bnop->funcUnit = FuncUnit::S1;
            } else {
                bnop->funcUnit = FuncUnit::S2;
            }
            bnop->PROT = dspInstInfo->getPROT();
            bnop->RS = dspInstInfo->getRS();
            bnop->BR = dspInstInfo->getBR();
            bnop->DSZ = dspInstInfo->getDSZ();
            bnop->SAT = dspInstInfo->getSAT();
            bnop->p = dspInstInfo->getP();
            bnop->fixUpRegOnCompactInstruction();
            bnop->instFormat = InstFormat::Kind2;
        } else if (checkBits<6>(data, 1, 0b110111) && checkBits<2>(data, 11, 0b00)) {
            bnop = std::make_shared<BNOP>(dspInstInfo->getAddress(), baseCycle);
            bnop->isCompactInstruction = true;
            bnop->compactInstKind = "Sx1b";
            auto cst3 = extractBits<3>(data, 13).to_ulong();
            bnop->numOfInsertNOP = cst3;
            bnop->src2 = extractBits<4>(data, 7).to_ulong();
            bnop->s = data[0];
            bnop->funcUnit = FuncUnit::S2;
            bnop->PROT = dspInstInfo->getPROT();
            bnop->RS = dspInstInfo->getRS();
            bnop->BR = dspInstInfo->getBR();
            bnop->DSZ = dspInstInfo->getDSZ();
            bnop->SAT = dspInstInfo->getSAT();
            bnop->p = dspInstInfo->getP();
            bnop->fixUpRegOnCompactInstruction();
            bnop->instFormat = InstFormat::Kind3;
        }
    }
    return bnop;
}

void BNOP::executeCustom(std::shared_ptr<TargetMachine> targetMachine, ExecutePhase executePhase) {
    if (instFormat == InstFormat::Kind1) {
//        BNOP .S BNOP (.unit) src2, src1 if instruction is not within compact instruction fetch packet
        if (executePhase == ExecutePhase::E6) {
            auto pce1 = *cRegPtr;
            auto pc = targetMachine->getRegisters()->getReg32ByName("pc");
            auto cst = *cstPtr;
            auto scst12 = extractBitsAndToInt<12>(cst);
            pc->updateData(pce1 + (scst12 << 2));
        }
    } else if (instFormat == InstFormat::Kind2) {
//        BNOP .S BNOP (.unit) src2, src1 f instruction is within compact instruction fetch packet
        if (executePhase == ExecutePhase::E6) {
            auto pce1 = *cRegPtr;
            auto pc = targetMachine->getRegisters()->getReg32ByName("pc");
            auto cst = *cstPtr;
            auto scst12 = extractBitsAndToInt<12>(cst);
            pc->updateData(pce1 + (scst12 << 1));
        }
    } else if (instFormat == InstFormat::Kind3) {
        if (executePhase == ExecutePhase::E6) {
            auto src2Reg = *src2LoRegPtr;
            targetMachine->getRegisters()->getReg32ByName("pc")->updateData(src2Reg);
        }
    }
}

std::shared_ptr<DspInstruction> BNOP::clone() {
    return std::make_shared<BNOP>(*this);
}

std::string BNOP::toString() const {
    std::string res;
    if (isCompactInstruction) {
        if (compactInstKind == "Sbs7c") {
            res += getSbs7cCondition() + " ";
        }
    } else {
        res += getStandardConditionString() + " ";
    }
    res += m_name + " " + FuncUnitName[static_cast<u32>(funcUnit)] + " ";
    switch (instFormat) {
        case InstFormat::Kind1:
        case InstFormat::Kind2:
            res += dec2hex(extractBitsAndToInt<12>(cst)) + "," + std::to_string(numOfInsertNOP);
            break;
        case InstFormat::Kind3:
            res = res + getSrc2Reg32Name() + "," + std::to_string(numOfInsertNOP);
            break;
        default:
            break;
    }
    return res;
}

bool BNOP::checkSbs7cCondition(std::shared_ptr<DspMachine> dspMachine) const {
    if (s == 0 && z == 0) {
        return *dspMachine->getSaveRegs()->getReg32ByName(RegAName[static_cast<u32>(RegAKind::A0)])->getDataPtr() != 0;
    } else if (s == 0 && z == 1) {
        return *dspMachine->getSaveRegs()->getReg32ByName(RegAName[static_cast<u32>(RegAKind::A0)])->getDataPtr() == 0;
    } else if (s == 1 && z == 0) {
        return *dspMachine->getSaveRegs()->getReg32ByName(RegBName[static_cast<u32>(RegBKind::B0)])->getDataPtr() != 0;
    } else {
        return *dspMachine->getSaveRegs()->getReg32ByName(RegBName[static_cast<u32>(RegBKind::B0)])->getDataPtr() == 0;
    }
}

std::string BNOP::getSbs7cCondition() const {
    if (s == 0 && z == 0) {
        return "[A0]";
    } else if (s == 0 && z == 1) {
        return "[!A0]";
    } else if (s == 1 && z == 0) {
        return "[B0]";
    } else {
        return "[!B0]";
    }
}
